import { type ViewType } from 'nocodb-sdk'
import type { ExtensionManifest, ExtensionType } from '#imports'

const [useProvideExtensionHelper, useExtensionHelper] = useInjectionState(
  (
    extension: Ref<ExtensionType>,
    extensionManifest: ComputedRef<ExtensionManifest | undefined>,
    activeError: Ref<any>,
    hasAccessToExtension: ComputedRef<boolean>,
  ) => {
    const { $api } = useNuxtApp()
    const route = useRoute()

    const basesStore = useBases()

    const { activeProjectId: baseId } = storeToRefs(basesStore)

    const tableStore = useTablesStore()

    const { activeTables: tables } = storeToRefs(tableStore)

    const viewStore = useViewsStore()

    const { viewsByTable } = storeToRefs(viewStore)

    const { getMeta } = useMetas()

    const { eventBus } = useSmartsheetStoreOrThrow()

    const fullscreen = ref(false)

    const showExpandBtn = ref(true)

    const fullscreenModalSize = ref<keyof typeof modalSizes>(extensionManifest.value?.config?.modalSize || 'lg')

    const disableToggleFullscreenBtn = ref(false)

    const activeTableId = computed(() => route.params.viewId as string | undefined)
    const activeViewId = computed(() => route.params.viewTitle as string | undefined)

    const collapsed = computed({
      get: () => extension.value?.meta?.collapsed ?? false,
      set: (value) => {
        extension.value?.setMeta('collapsed', value)
      },
    })

    const getViewsForTable = async (tableId: string) => {
      if (viewsByTable.value.has(tableId)) {
        return viewsByTable.value.get(tableId) as ViewType[]
      }

      await viewStore.loadViews({ tableId, ignoreLoading: true })
      return viewsByTable.value.get(tableId) as ViewType[]
    }

    const getData = async (params: {
      tableId: string
      viewId?: string
      eachPage: (records: Record<string, any>[], nextPage: () => void) => Promise<void> | void
      done: () => Promise<void> | void
    }) => {
      const { tableId, viewId, eachPage, done } = params

      let page = 1

      const nextPage = async () => {
        const { list: records, pageInfo } = await $api.dbViewRow.list(
          'noco',
          baseId.value!,
          tableId,
          viewId as string,
          {
            offset: (page - 1) * 100,
            limit: 100,
          } as any,
        )

        if (pageInfo?.isLastPage) {
          await eachPage(records, () => {})
          await done()
        } else {
          page++
          await eachPage(records, nextPage)
        }
      }

      await nextPage()
    }

    const getTableMeta = async (tableId: string) => {
      return getMeta(tableId)
    }

    const insertData = async (params: { tableId: string; data: Record<string, any>[]; autoInsertOption?: boolean }) => {
      const { tableId, data } = params

      const chunks = []

      let inserted = 0

      // chunk data into 100 records
      for (let i = 0; i < data.length; i += 100) {
        chunks.push(data.slice(i, i + 100))
      }

      for (const chunk of chunks) {
        inserted += chunk.length
        await $api.dbDataTableRow.create(tableId, chunk, params.autoInsertOption ? ({ typecast: 'true' } as any) : undefined)
      }

      return {
        inserted,
      }
    }

    const updateData = async (params: { tableId: string; data: Record<string, any>[] }) => {
      const { tableId, data } = params

      const chunks = []

      let updated = 0

      // chunk data into 100 records
      for (let i = 0; i < data.length; i += 100) {
        chunks.push(data.slice(i, i + 100))
      }

      for (const chunk of chunks) {
        updated += chunk.length
        await $api.dbDataTableRow.update(tableId, chunk)
      }

      return {
        updated,
      }
    }

    const upsertData = async (params: {
      tableId: string
      autoInsertOption?: boolean
      insert: Record<string, any>[]
      update: Record<string, any>[]
    }) => {
      const { tableId, insert, update } = params

      const chunkSize = 100

      const tableMeta = await getMeta(tableId)

      if (!tableMeta?.columns) throw new Error('Table not found')

      let insertCounter = 0
      let updateCounter = 0

      if (insert.length) {
        insertCounter += insert.length
        while (insert.length) {
          await $api.dbDataTableRow.create(
            tableId,
            insert.splice(0, chunkSize),
            params.autoInsertOption ? ({ typecast: 'true' } as any) : undefined,
          )
        }
      }

      if (update.length) {
        updateCounter += update.length
        while (update.length) {
          await $api.dbDataTableRow.update(
            tableId,
            update.splice(0, chunkSize),
            params.autoInsertOption ? ({ typecast: 'true' } as any) : undefined,
          )
        }
      }

      return { inserted: insertCounter, updated: updateCounter }
    }

    const reloadData = () => {
      eventBus.emit(SmartsheetStoreEvents.DATA_RELOAD)
    }

    const reloadMeta = () => {
      eventBus.emit(SmartsheetStoreEvents.FIELD_RELOAD)
    }

    return {
      $api,
      fullscreen,
      collapsed,
      extension,
      extensionManifest,
      activeError,
      tables,
      showExpandBtn,
      fullscreenModalSize,
      activeTableId,
      activeViewId,
      getViewsForTable,
      getData,
      getTableMeta,
      insertData,
      updateData,
      upsertData,
      reloadData,
      reloadMeta,
      eventBus,
      hasAccessToExtension,
      disableToggleFullscreenBtn,
    }
  },
  'extension-helper',
)

export { useProvideExtensionHelper }

export function useExtensionHelperOrThrow() {
  const extensionStore = useExtensionHelper()
  if (extensionStore == null) throw new Error('Please call `useProvideExtensionHelper` on the appropriate parent component')
  return extensionStore
}
